import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import yfinance as yf
import plotly.io as pioPlotly : pandas backend
yfinance와plotly를 이용하여 자료를 받고 시각화해보자!
해당 포스트는 전북대학교 통계학과 최규빈 교수님의 강의내용을 토대로 재구성되었음을 알립니다.
1. 라이브러리 imports
pd.options.plotting.backend = 'plotly'
pio.templates.default = 'plotly_white'
print(pio.templates)Templates configuration
-----------------------
Default template: 'plotly_white'
Available templates:
['ggplot2', 'seaborn', 'simple_white', 'plotly',
'plotly_white', 'plotly_dark', 'presentation', 'xgridoff',
'ygridoff', 'gridon', 'none']
기본적으로 산출되는 옵션을 바꿔준다.
pandas의 디폴트 백엔드는matplotlib이기 때문에, 이것을plotly로 바꾸고 템플릿을 하얀색으로 바꿨다.
- 간단하게 backend = plotly를 입력하지 않아도 되게 만들었음.
2. yfinance를 이용한 주식 자료 시각화
A. 크롤링 + 데이터 정리
주식 종목에 따른 정보 가져오기(야후 파이낸셜에서 검색) :
Apple : AAPL
삼성전자 : 005930.KS
해당 코드를 이용하여 관심있는 데이터를 크롤링하려면?
symbols = ['AMZN','AAPL','GOOG','MSFT','NFLX','NVDA','TSLA'] ## 관심있는 주식들
start = '2020-01-01' ## 장 시작
end = '2023-11-06' ## 장 종료
df = yf.download(symbols,start,end)[*********************100%%**********************] 7 of 7 completed
df| Adj Close | Close | ... | Open | Volume | |||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| AAPL | AMZN | GOOG | MSFT | NFLX | NVDA | TSLA | AAPL | AMZN | GOOG | ... | NFLX | NVDA | TSLA | AAPL | AMZN | GOOG | MSFT | NFLX | NVDA | TSLA | |
| Date | |||||||||||||||||||||
| 2020-01-02 | 73.152657 | 94.900497 | 68.368500 | 155.093674 | 329.809998 | 59.749290 | 28.684000 | 75.087502 | 94.900497 | 68.368500 | ... | 326.100006 | 59.687500 | 28.299999 | 135480400 | 80580000 | 28132000 | 22622100 | 4485800 | 23753600 | 142981500 |
| 2020-01-03 | 72.441460 | 93.748497 | 68.032997 | 153.162476 | 325.899994 | 58.792957 | 29.534000 | 74.357498 | 93.748497 | 68.032997 | ... | 326.779999 | 58.775002 | 29.366667 | 146322800 | 75288000 | 23728000 | 21116200 | 3806900 | 20538400 | 266677500 |
| 2020-01-06 | 73.018692 | 95.143997 | 69.710503 | 153.558395 | 335.829987 | 59.039509 | 30.102667 | 74.949997 | 95.143997 | 69.710503 | ... | 323.119995 | 58.080002 | 29.364668 | 118387200 | 81236000 | 34646000 | 20813700 | 5663100 | 26263600 | 151995000 |
| 2020-01-07 | 72.675285 | 95.343002 | 69.667000 | 152.158249 | 330.750000 | 59.754276 | 31.270666 | 74.597504 | 95.343002 | 69.667000 | ... | 336.470001 | 59.549999 | 30.760000 | 108872000 | 80898000 | 30054000 | 21634100 | 4703200 | 31485600 | 268231500 |
| 2020-01-08 | 73.844353 | 94.598503 | 70.216003 | 154.581940 | 339.260010 | 59.866344 | 32.809334 | 75.797501 | 94.598503 | 70.216003 | ... | 331.489990 | 59.939999 | 31.580000 | 132079200 | 70160000 | 30560000 | 27746500 | 7104500 | 27710800 | 467164500 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 2023-10-30 | 170.065933 | 132.710007 | 125.750000 | 337.309998 | 410.079987 | 411.609985 | 197.360001 | 170.289993 | 132.710007 | 125.750000 | ... | 402.350006 | 410.869995 | 209.279999 | 51131000 | 72485500 | 24165600 | 22828100 | 5317100 | 38802800 | 136448200 |
| 2023-10-31 | 170.545319 | 133.089996 | 125.300003 | 338.109985 | 411.690002 | 407.799988 | 200.839996 | 170.770004 | 133.089996 | 125.300003 | ... | 409.239990 | 404.500000 | 196.119995 | 44846000 | 51589400 | 21123400 | 20265300 | 3877600 | 51796900 | 118068300 |
| 2023-11-01 | 173.741104 | 137.000000 | 127.570000 | 346.070007 | 420.190002 | 423.250000 | 205.660004 | 173.970001 | 137.000000 | 127.570000 | ... | 414.769989 | 408.839996 | 204.039993 | 56934900 | 61529400 | 26536600 | 28158800 | 4806100 | 43759300 | 121661700 |
| 2023-11-02 | 177.336380 | 138.070007 | 128.580002 | 348.320007 | 424.709991 | 435.059998 | 218.509995 | 177.570007 | 138.070007 | 128.580002 | ... | 421.170013 | 433.279999 | 212.970001 | 77334800 | 52236700 | 24091700 | 24348100 | 4476000 | 40917200 | 125987600 |
| 2023-11-03 | 176.417572 | 138.600006 | 130.369995 | 352.799988 | 432.359985 | 450.049988 | 219.960007 | 176.649994 | 138.600006 | 130.369995 | ... | 428.760010 | 440.200012 | 221.149994 | 79763700 | 44007200 | 19517900 | 23624000 | 3664800 | 42385500 | 119281000 |
968 rows × 42 columns
wide data로 들어가있는 것을 알 수 있다.
- 그럼 필요한 정보(조정된 주가)만 가져와보자.
df.stack().loc[:, 'Adj Close'].reset_index()| Date | level_1 | Adj Close | |
|---|---|---|---|
| 0 | 2020-01-02 | AAPL | 73.152657 |
| 1 | 2020-01-02 | AMZN | 94.900497 |
| 2 | 2020-01-02 | GOOG | 68.368500 |
| 3 | 2020-01-02 | MSFT | 155.093674 |
| 4 | 2020-01-02 | NFLX | 329.809998 |
| ... | ... | ... | ... |
| 6771 | 2023-11-03 | GOOG | 130.369995 |
| 6772 | 2023-11-03 | MSFT | 352.799988 |
| 6773 | 2023-11-03 | NFLX | 432.359985 |
| 6774 | 2023-11-03 | NVDA | 450.049988 |
| 6775 | 2023-11-03 | TSLA | 219.960007 |
6776 rows × 3 columns
- 다른 방법(MultiIndex 이용)
df.columnsMultiIndex([('Adj Close', 'AAPL'),
('Adj Close', 'AMZN'),
('Adj Close', 'GOOG'),
('Adj Close', 'MSFT'),
('Adj Close', 'NFLX'),
('Adj Close', 'NVDA'),
('Adj Close', 'TSLA'),
( 'Close', 'AAPL'),
( 'Close', 'AMZN'),
( 'Close', 'GOOG'),
( 'Close', 'MSFT'),
( 'Close', 'NFLX'),
( 'Close', 'NVDA'),
( 'Close', 'TSLA'),
( 'High', 'AAPL'),
( 'High', 'AMZN'),
( 'High', 'GOOG'),
( 'High', 'MSFT'),
( 'High', 'NFLX'),
( 'High', 'NVDA'),
( 'High', 'TSLA'),
( 'Low', 'AAPL'),
( 'Low', 'AMZN'),
( 'Low', 'GOOG'),
( 'Low', 'MSFT'),
( 'Low', 'NFLX'),
( 'Low', 'NVDA'),
( 'Low', 'TSLA'),
( 'Open', 'AAPL'),
( 'Open', 'AMZN'),
( 'Open', 'GOOG'),
( 'Open', 'MSFT'),
( 'Open', 'NFLX'),
( 'Open', 'NVDA'),
( 'Open', 'TSLA'),
( 'Volume', 'AAPL'),
( 'Volume', 'AMZN'),
( 'Volume', 'GOOG'),
( 'Volume', 'MSFT'),
( 'Volume', 'NFLX'),
( 'Volume', 'NVDA'),
( 'Volume', 'TSLA')],
)
이렇게 멀티인덱스로 되어있는 경우, 앞의 것만으로도 호출할 수 있다. 예를들어…
df.loc[:, 'Adj Close'] ## 이건 된다.
#df.loc[:, 'AAPL'] ## 이건 안된다.| AAPL | AMZN | GOOG | MSFT | NFLX | NVDA | TSLA | |
|---|---|---|---|---|---|---|---|
| Date | |||||||
| 2020-01-02 | 73.152657 | 94.900497 | 68.368500 | 155.093674 | 329.809998 | 59.749290 | 28.684000 |
| 2020-01-03 | 72.441460 | 93.748497 | 68.032997 | 153.162476 | 325.899994 | 58.792957 | 29.534000 |
| 2020-01-06 | 73.018692 | 95.143997 | 69.710503 | 153.558395 | 335.829987 | 59.039509 | 30.102667 |
| 2020-01-07 | 72.675285 | 95.343002 | 69.667000 | 152.158249 | 330.750000 | 59.754276 | 31.270666 |
| 2020-01-08 | 73.844353 | 94.598503 | 70.216003 | 154.581940 | 339.260010 | 59.866344 | 32.809334 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 2023-10-30 | 170.065933 | 132.710007 | 125.750000 | 337.309998 | 410.079987 | 411.609985 | 197.360001 |
| 2023-10-31 | 170.545319 | 133.089996 | 125.300003 | 338.109985 | 411.690002 | 407.799988 | 200.839996 |
| 2023-11-01 | 173.741104 | 137.000000 | 127.570000 | 346.070007 | 420.190002 | 423.250000 | 205.660004 |
| 2023-11-02 | 177.336380 | 138.070007 | 128.580002 | 348.320007 | 424.709991 | 435.059998 | 218.509995 |
| 2023-11-03 | 176.417572 | 138.600006 | 130.369995 | 352.799988 | 432.359985 | 450.049988 | 219.960007 |
968 rows × 7 columns
- 굳이굳이 위와 같은 방식으로 AAPL만 추출하고 싶다면…
df.stack().stack().swaplevel(i = 1, j = 2) ## 멀티인덱스(level)의 순서를 바꿔줌.Date
2020-01-02 Adj Close AAPL 7.315266e+01
Close AAPL 7.508750e+01
High AAPL 7.515000e+01
Low AAPL 7.379750e+01
Open AAPL 7.406000e+01
...
2023-11-03 Close TSLA 2.199600e+02
High TSLA 2.263700e+02
Low TSLA 2.184000e+02
Open TSLA 2.211500e+02
Volume TSLA 1.192810e+08
Length: 40656, dtype: float64
df.stack().stack().swaplevel(i = 1, j = 2).unstack().unstack().loc[:, 'AAPL']| Adj Close | Close | High | Low | Open | Volume | |
|---|---|---|---|---|---|---|
| Date | ||||||
| 2020-01-02 | 73.152657 | 75.087502 | 75.150002 | 73.797501 | 74.059998 | 135480400.0 |
| 2020-01-03 | 72.441460 | 74.357498 | 75.144997 | 74.125000 | 74.287498 | 146322800.0 |
| 2020-01-06 | 73.018692 | 74.949997 | 74.989998 | 73.187500 | 73.447502 | 118387200.0 |
| 2020-01-07 | 72.675285 | 74.597504 | 75.224998 | 74.370003 | 74.959999 | 108872000.0 |
| 2020-01-08 | 73.844353 | 75.797501 | 76.110001 | 74.290001 | 74.290001 | 132079200.0 |
| ... | ... | ... | ... | ... | ... | ... |
| 2023-10-30 | 170.065933 | 170.289993 | 171.169998 | 168.869995 | 169.020004 | 51131000.0 |
| 2023-10-31 | 170.545319 | 170.770004 | 170.899994 | 167.899994 | 169.350006 | 44846000.0 |
| 2023-11-01 | 173.741104 | 173.970001 | 174.229996 | 170.119995 | 171.000000 | 56934900.0 |
| 2023-11-02 | 177.336380 | 177.570007 | 177.779999 | 175.460007 | 175.520004 | 77334800.0 |
| 2023-11-03 | 176.417572 | 176.649994 | 176.820007 | 173.350006 | 174.240005 | 79763700.0 |
968 rows × 6 columns
이렇게 하면 된다.
df.swaplevel(i = 0, j = 1, axis = 1).loc[:, 'AAPL']| Adj Close | Close | High | Low | Open | Volume | |
|---|---|---|---|---|---|---|
| Date | ||||||
| 2020-01-02 | 73.152657 | 75.087502 | 75.150002 | 73.797501 | 74.059998 | 135480400 |
| 2020-01-03 | 72.441460 | 74.357498 | 75.144997 | 74.125000 | 74.287498 | 146322800 |
| 2020-01-06 | 73.018692 | 74.949997 | 74.989998 | 73.187500 | 73.447502 | 118387200 |
| 2020-01-07 | 72.675285 | 74.597504 | 75.224998 | 74.370003 | 74.959999 | 108872000 |
| 2020-01-08 | 73.844353 | 75.797501 | 76.110001 | 74.290001 | 74.290001 | 132079200 |
| ... | ... | ... | ... | ... | ... | ... |
| 2023-10-30 | 170.065933 | 170.289993 | 171.169998 | 168.869995 | 169.020004 | 51131000 |
| 2023-10-31 | 170.545319 | 170.770004 | 170.899994 | 167.899994 | 169.350006 | 44846000 |
| 2023-11-01 | 173.741104 | 173.970001 | 174.229996 | 170.119995 | 171.000000 | 56934900 |
| 2023-11-02 | 177.336380 | 177.570007 | 177.779999 | 175.460007 | 175.520004 | 77334800 |
| 2023-11-03 | 176.417572 | 176.649994 | 176.820007 | 173.350006 | 174.240005 | 79763700 |
968 rows × 6 columns
이것도 똑같다.(물론 컬럼 인덱스가 정렬이 안되어있는 게 다르긴한데, 결과는 똑같잖아?)
- 솔직히 아래와 같이 하는 게 제일 맘편하다.
df.stack().reset_index().rename({'level_1' : 'Subject'}, axis = 1) ## 바로 타이디데이터로| Date | Subject | Adj Close | Close | High | Low | Open | Volume | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2020-01-02 | AAPL | 73.152657 | 75.087502 | 75.150002 | 73.797501 | 74.059998 | 135480400 |
| 1 | 2020-01-02 | AMZN | 94.900497 | 94.900497 | 94.900497 | 93.207497 | 93.750000 | 80580000 |
| 2 | 2020-01-02 | GOOG | 68.368500 | 68.368500 | 68.406998 | 67.077499 | 67.077499 | 28132000 |
| 3 | 2020-01-02 | MSFT | 155.093674 | 160.619995 | 160.729996 | 158.330002 | 158.779999 | 22622100 |
| 4 | 2020-01-02 | NFLX | 329.809998 | 329.809998 | 329.980011 | 324.779999 | 326.100006 | 4485800 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 6771 | 2023-11-03 | GOOG | 130.369995 | 130.369995 | 130.729996 | 129.009995 | 129.089996 | 19517900 |
| 6772 | 2023-11-03 | MSFT | 352.799988 | 352.799988 | 354.390015 | 347.329987 | 349.630005 | 23624000 |
| 6773 | 2023-11-03 | NFLX | 432.359985 | 432.359985 | 434.820007 | 425.529999 | 428.760010 | 3664800 |
| 6774 | 2023-11-03 | NVDA | 450.049988 | 450.049988 | 453.089996 | 437.230011 | 440.200012 | 42385500 |
| 6775 | 2023-11-03 | TSLA | 219.960007 | 219.960007 | 226.369995 | 218.399994 | 221.149994 | 119281000 |
6776 rows × 8 columns
### B. 시각화
- tidydata를 생성
df.loc[:, 'Adj Close'].reset_index().set_index('Date').stack().reset_index().rename({'level_1' : 'Company', 0 : 'Price'}, axis = 1)| Date | Company | Price | |
|---|---|---|---|
| 0 | 2020-01-02 | AAPL | 73.152657 |
| 1 | 2020-01-02 | AMZN | 94.900497 |
| 2 | 2020-01-02 | GOOG | 68.368500 |
| 3 | 2020-01-02 | MSFT | 155.093674 |
| 4 | 2020-01-02 | NFLX | 329.809998 |
| ... | ... | ... | ... |
| 6771 | 2023-11-03 | GOOG | 130.369995 |
| 6772 | 2023-11-03 | MSFT | 352.799988 |
| 6773 | 2023-11-03 | NFLX | 432.359985 |
| 6774 | 2023-11-03 | NVDA | 450.049988 |
| 6775 | 2023-11-03 | TSLA | 219.960007 |
6776 rows × 3 columns
- 시각화 : 데이터프레임 자체 메소드 활용
df.loc[:, 'Adj Close'].reset_index().set_index('Date').stack().reset_index().rename({'level_1' : 'Company', 0 : 'Price'}, axis = 1)\
.plot.line(x = 'Date', y = 'Price', color = 'Company', backend = 'plotly') ## plot()은 라인이 디폴트긴 함.깔쌈하죠?
상당히 강력한 툴임, 이미지적으로 지원하는 게 되게 많음.
3. 출산률 시각화
A. 크롤링 + 데이터 정리
- 대한민국의 저출산 문제
ref : https://ko.wikipedia.org/wiki/대한민국의_저출산
- 위의 url에서 3, 5번째 테이블만 읽고 싶다면 어떻게 해야 할까???
- 3번째 테이블 : 시도별 출산률
- 5번째 테이블 : 시도별 출생아 수
1 데이터 긁어오기
df_lst = pd.read_html('https://ko.wikipedia.org/wiki/%EB%8C%80%ED%95%9C%EB%AF%BC%EA%B5%AD%EC%9D%98_%EC%A0%80%EC%B6%9C%EC%82%B0')예, ㅈㄴ 단순합니다. 일단 판다스에서 자체적으로 html을 긁어올 수 있어요.
len(df_lst) ## 22개의 테뷸러 데이터22
df_lst[2] ## 시도별 합계출산률| 지역/연도[6] | 2005 | 2006[7] | 2007 | 2008[8] | 2009[9] | 2010 | 2011 | 2012 | 2013 | 2014 | 2015 | 2016 | 2017 | 2018 | 2019 | 2020 | 2021 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 서울 | 0.92 | 0.97 | 1.06 | 1.01 | 0.96 | 1.02 | 1.01 | 1.06 | 0.97 | 0.98 | 1.00 | 0.94 | 0.84 | 0.76 | 0.72 | 0.64 | 0.63 |
| 1 | 부산 | 0.88 | 0.91 | 1.02 | 0.98 | 0.94 | 1.05 | 1.08 | 1.14 | 1.05 | 1.09 | 1.14 | 1.10 | 0.98 | 0.90 | 0.83 | 0.75 | 0.73 |
| 2 | 대구 | 0.99 | 1.00 | 1.13 | 1.07 | 1.03 | 1.11 | 1.15 | 1.22 | 1.13 | 1.17 | 1.22 | 1.19 | 1.07 | 0.99 | 0.93 | 0.81 | 0.78 |
| 3 | 인천 | 1.07 | 1.11 | 1.25 | 1.19 | 1.14 | 1.21 | 1.23 | 1.30 | 1.20 | 1.21 | 1.22 | 1.14 | 1.01 | 1.01 | 0.94 | 0.83 | 0.78 |
| 4 | 광주 | 1.10 | 1.14 | 1.26 | 1.20 | 1.14 | 1.22 | 1.23 | 1.30 | 1.17 | 1.20 | 1.21 | 1.17 | 1.05 | 0.97 | 0.91 | 0.81 | 0.90 |
| 5 | 대전 | 1.10 | 1.15 | 1.27 | 1.22 | 1.16 | 1.21 | 1.26 | 1.32 | 1.23 | 1.25 | 1.28 | 1.19 | 1.08 | 0.95 | 0.88 | 0.81 | 0.81 |
| 6 | 울산 | 1.18 | 1.24 | 1.40 | 1.34 | 1.31 | 1.37 | 1.39 | 1.48 | 1.39 | 1.44 | 1.49 | 1.42 | 1.26 | 1.13 | 1.08 | 0.99 | 0.94 |
| 7 | 세종 | - | - | - | - | - | - | - | 1.60 | 1.44 | 1.35 | 1.89 | 1.82 | 1.67 | 1.57 | 1.47 | 1.28 | 1.28 |
| 8 | 경기 | 1.17 | 1.23 | 1.35 | 1.29 | 1.23 | 1.31 | 1.31 | 1.36 | 1.23 | 1.24 | 1.27 | 1.19 | 1.07 | 1.00 | 0.94 | 0.88 | 0.85 |
| 9 | 강원 | 1.18 | 1.19 | 1.35 | 1.25 | 1.25 | 1.31 | 1.34 | 1.37 | 1.25 | 1.25 | 1.31 | 1.24 | 1.12 | 1.07 | 1.08 | 1.04 | 0.98 |
| 10 | 충북 | 1.19 | 1.22 | 1.39 | 1.32 | 1.32 | 1.40 | 1.43 | 1.49 | 1.37 | 1.36 | 1.41 | 1.36 | 1.24 | 1.17 | 1.05 | 0.98 | 0.95 |
| 11 | 충남 | 1.26 | 1.35 | 1.50 | 1.44 | 1.41 | 1.48 | 1.50 | 1.57 | 1.44 | 1.42 | 1.48 | 1.40 | 1.28 | 1.19 | 1.11 | 1.03 | 0.96 |
| 12 | 전북 | 1.17 | 1.20 | 1.37 | 1.31 | 1.28 | 1.37 | 1.41 | 1.44 | 1.32 | 1.33 | 1.35 | 1.25 | 1.15 | 1.04 | 0.97 | 0.91 | 0.85 |
| 13 | 전남 | 1.28 | 1.33 | 1.53 | 1.45 | 1.45 | 1.54 | 1.57 | 1.64 | 1.52 | 1.50 | 1.55 | 1.47 | 1.33 | 1.24 | 1.23 | 1.15 | 1.02 |
| 14 | 경북 | 1.17 | 1.20 | 1.36 | 1.31 | 1.27 | 1.38 | 1.43 | 1.49 | 1.38 | 1.41 | 1.46 | 1.40 | 1.26 | 1.17 | 1.09 | 1.00 | 0.97 |
| 15 | 경남 | 1.18 | 1.25 | 1.43 | 1.37 | 1.32 | 1.41 | 1.45 | 1.50 | 1.37 | 1.41 | 1.44 | 1.36 | 1.23 | 1.12 | 1.05 | 0.95 | 0.90 |
| 16 | 제주 | 1.30 | 1.36 | 1.48 | 1.39 | 1.38 | 1.46 | 1.49 | 1.60 | 1.43 | 1.48 | 1.48 | 1.43 | 1.31 | 1.22 | 1.15 | 1.02 | 0.95 |
| 17 | 전국 | 1.08 | 1.13 | 1.25 | 1.19 | 1.15 | 1.23 | 1.24 | 1.30 | 1.19 | 1.21 | 1.24 | 1.17 | 1.05 | 0.98 | 0.92 | 0.84 | 0.81 |
df_lst[4] ## 시도별 출생아 수| 지역/연도[6] | 2010 | 2011 | 2012 | 2013 | 2014 | 2015 | 2016 | 2017 | 2018 | 2019 | 2020 | 2021 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 서울 | 93266 | 91526 | 93914.000 | 84066.000 | 83711.000 | 83005 | 75.536 | 65389 | 58074 | 53.673 | 47400 | 45531 |
| 1 | 부산 | 27415 | 27759 | 28673.000 | 25831.000 | 26190.000 | 26645 | 24906.000 | 21480 | 19152 | 17049.000 | 15100 | 14446 |
| 2 | 대구 | 20557 | 20758 | 21472.000 | 19340.000 | 19361.000 | 19438 | 18298.000 | 15946 | 14400 | 13233.000 | 11200 | 10661 |
| 3 | 인천 | 25752 | 20758 | 21472.000 | 25560.000 | 25786.000 | 25491 | 23609.000 | 20445 | 20087 | 18522.000 | 16000 | 14947 |
| 4 | 광주 | 13979 | 13916 | 14392.000 | 12729.000 | 12729.000 | 12441 | 11580.000 | 10120 | 9105 | 8364.000 | 7300 | 7956 |
| 5 | 대전 | 14314 | 14808 | 15279.000 | 14099.000 | 13962.000 | 13774 | 12436.000 | 10851 | 9337 | 8410.000 | 7500 | 7414 |
| 6 | 울산 | 11432 | 11542 | 12160.000 | 11330.000 | 11556.000 | 11732 | 10910.000 | 9381 | 8149 | 7539.000 | 6600 | 6127 |
| 7 | 세종 | - | - | 1054.000 | 1111.000 | 1344.000 | 2708 | 3297.000 | 3504 | 3703 | 3819.000 | 3500 | 3570 |
| 8 | 경기 | 121753 | 122027 | 124746.000 | 112129.000 | 112.169 | 113495 | 105643.000 | 94088 | 83198 | 83.198 | 77800 | 76139 |
| 9 | 강원 | 12477 | 12408 | 12426.000 | 10980.000 | 10662.000 | 10929 | 10058.000 | 9958 | 8351 | 8283.000 | 7800 | 7357 |
| 10 | 충북 | 14670 | 14804 | 15139.000 | 13658.000 | 13366.000 | 13563 | 12742.000 | 11394 | 10586 | 9333.000 | 8600 | 8190 |
| 11 | 충남 | 20.242 | 20.398 | 20.448 | 18.628 | 18200.000 | 18604 | 17302.000 | 15670 | 14380 | 13228.000 | 11900 | 10984 |
| 12 | 전북 | 16100 | 16175 | 16238.000 | 14555.000 | 14231.000 | 14087 | 12698.000 | 11348 | 10001 | 8971.000 | 8200 | 7745 |
| 13 | 전남 | 16654 | 16612 | 16990.000 | 15401.000 | 14817.000 | 15061 | 13980.000 | 12354 | 11238 | 10832.000 | 9700 | 8430 |
| 14 | 경북 | 23700 | 24250 | 24635.000 | 22206.000 | 22062.000 | 22310 | 20616.000 | 17957 | 16079 | 14472.000 | 12900 | 12045 |
| 15 | 경남 | 32203 | 32536 | 33211.000 | 29504.000 | 29763.000 | 29537 | 27138.000 | 23849 | 21224 | 19250.000 | 16800 | 15562 |
| 16 | 제주 | 5657 | 5628 | 5992.000 | 5328.000 | 5526.000 | 5600 | 5494.000 | 5037 | 4781 | 4500.000 | 4000 | 3728 |
| 17 | 전국 | 470171 | 471265 | 484550.000 | 436455.000 | 435435.000 | 438420 | 406243.000 | 357771 | 326822 | 302676.000 | 272400 | 260562 |
세종시의 경우 중간에 지역이 추가되어 결측치가
-로 들어가 있음
2 데이터 처리
df = df_lst[4]
df.info()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 18 entries, 0 to 17
Data columns (total 13 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 지역/연도[6] 18 non-null object
1 2010 18 non-null object
2 2011 18 non-null object
3 2012 18 non-null float64
4 2013 18 non-null float64
5 2014 18 non-null float64
6 2015 18 non-null int64
7 2016 18 non-null float64
8 2017 18 non-null int64
9 2018 18 non-null int64
10 2019 18 non-null float64
11 2020 18 non-null int64
12 2021 18 non-null int64
dtypes: float64(5), int64(5), object(3)
memory usage: 2.0+ KB
출생아 수이니까 지역빼고 전부 다 int64여야 할텐데, 1-2는 object임
df.rename({'지역/연도[6]' : '지역'}, axis = 1).set_index('지역').applymap(lambda x : float(x) if x != '-' else 0).reset_index() ## 어? 열 이름 영어로 안바꿔요???
## applymap이 map으로 명령어가 바뀌었네... 근데 일단 이거 써야지 뭐.C:\Users\hollyriver\AppData\Local\Temp\ipykernel_144\615695314.py:1: FutureWarning:
DataFrame.applymap has been deprecated. Use DataFrame.map instead.
| 지역 | 2010 | 2011 | 2012 | 2013 | 2014 | 2015 | 2016 | 2017 | 2018 | 2019 | 2020 | 2021 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 서울 | 93266.000 | 91526.000 | 93914.000 | 84066.000 | 83711.000 | 83005.0 | 75.536 | 65389.0 | 58074.0 | 53.673 | 47400.0 | 45531.0 |
| 1 | 부산 | 27415.000 | 27759.000 | 28673.000 | 25831.000 | 26190.000 | 26645.0 | 24906.000 | 21480.0 | 19152.0 | 17049.000 | 15100.0 | 14446.0 |
| 2 | 대구 | 20557.000 | 20758.000 | 21472.000 | 19340.000 | 19361.000 | 19438.0 | 18298.000 | 15946.0 | 14400.0 | 13233.000 | 11200.0 | 10661.0 |
| 3 | 인천 | 25752.000 | 20758.000 | 21472.000 | 25560.000 | 25786.000 | 25491.0 | 23609.000 | 20445.0 | 20087.0 | 18522.000 | 16000.0 | 14947.0 |
| 4 | 광주 | 13979.000 | 13916.000 | 14392.000 | 12729.000 | 12729.000 | 12441.0 | 11580.000 | 10120.0 | 9105.0 | 8364.000 | 7300.0 | 7956.0 |
| 5 | 대전 | 14314.000 | 14808.000 | 15279.000 | 14099.000 | 13962.000 | 13774.0 | 12436.000 | 10851.0 | 9337.0 | 8410.000 | 7500.0 | 7414.0 |
| 6 | 울산 | 11432.000 | 11542.000 | 12160.000 | 11330.000 | 11556.000 | 11732.0 | 10910.000 | 9381.0 | 8149.0 | 7539.000 | 6600.0 | 6127.0 |
| 7 | 세종 | 0.000 | 0.000 | 1054.000 | 1111.000 | 1344.000 | 2708.0 | 3297.000 | 3504.0 | 3703.0 | 3819.000 | 3500.0 | 3570.0 |
| 8 | 경기 | 121753.000 | 122027.000 | 124746.000 | 112129.000 | 112.169 | 113495.0 | 105643.000 | 94088.0 | 83198.0 | 83.198 | 77800.0 | 76139.0 |
| 9 | 강원 | 12477.000 | 12408.000 | 12426.000 | 10980.000 | 10662.000 | 10929.0 | 10058.000 | 9958.0 | 8351.0 | 8283.000 | 7800.0 | 7357.0 |
| 10 | 충북 | 14670.000 | 14804.000 | 15139.000 | 13658.000 | 13366.000 | 13563.0 | 12742.000 | 11394.0 | 10586.0 | 9333.000 | 8600.0 | 8190.0 |
| 11 | 충남 | 20.242 | 20.398 | 20.448 | 18.628 | 18200.000 | 18604.0 | 17302.000 | 15670.0 | 14380.0 | 13228.000 | 11900.0 | 10984.0 |
| 12 | 전북 | 16100.000 | 16175.000 | 16238.000 | 14555.000 | 14231.000 | 14087.0 | 12698.000 | 11348.0 | 10001.0 | 8971.000 | 8200.0 | 7745.0 |
| 13 | 전남 | 16654.000 | 16612.000 | 16990.000 | 15401.000 | 14817.000 | 15061.0 | 13980.000 | 12354.0 | 11238.0 | 10832.000 | 9700.0 | 8430.0 |
| 14 | 경북 | 23700.000 | 24250.000 | 24635.000 | 22206.000 | 22062.000 | 22310.0 | 20616.000 | 17957.0 | 16079.0 | 14472.000 | 12900.0 | 12045.0 |
| 15 | 경남 | 32203.000 | 32536.000 | 33211.000 | 29504.000 | 29763.000 | 29537.0 | 27138.000 | 23849.0 | 21224.0 | 19250.000 | 16800.0 | 15562.0 |
| 16 | 제주 | 5657.000 | 5628.000 | 5992.000 | 5328.000 | 5526.000 | 5600.0 | 5494.000 | 5037.0 | 4781.0 | 4500.000 | 4000.0 | 3728.0 |
| 17 | 전국 | 470171.000 | 471265.000 | 484550.000 | 436455.000 | 435435.000 | 438420.0 | 406243.000 | 357771.0 | 326822.0 | 302676.000 | 272400.0 | 260562.0 |
df.rename({'지역/연도[6]' : '지역'}, axis = 1).set_index('지역').applymap(lambda x : float(x) if x != '-' else 0).reset_index().info()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 18 entries, 0 to 17
Data columns (total 13 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 지역 18 non-null object
1 2010 18 non-null float64
2 2011 18 non-null float64
3 2012 18 non-null float64
4 2013 18 non-null float64
5 2014 18 non-null float64
6 2015 18 non-null float64
7 2016 18 non-null float64
8 2017 18 non-null float64
9 2018 18 non-null float64
10 2019 18 non-null float64
11 2020 18 non-null float64
12 2021 18 non-null float64
dtypes: float64(12), object(1)
memory usage: 2.0+ KB
C:\Users\hollyriver\AppData\Local\Temp\ipykernel_144\1650738614.py:1: FutureWarning:
DataFrame.applymap has been deprecated. Use DataFrame.map instead.
지역만 object로 바뀌고 나머지는 float(???)으로 잘 들어간 모습.
### B. 시각화 1 : 전국 출생아 수의 시각화
- 전국으로 따로 집계가 되어있긴 하지만, 실습을 위해(?) 따로 집계를 해서 산출해보도록 하자.
df.rename({'지역/연도[6]' : '지역'}, axis = 1).set_index('지역').applymap(lambda x : float(x) if x != '-' else 0).reset_index()\
.drop(17, axis = 0)\
.set_index('지역').stack().reset_index().rename({'level_1' : '연도', 0 : '출생아 수'}, axis = 1)C:\Users\hollyriver\AppData\Local\Temp\ipykernel_144\2390378994.py:1: FutureWarning:
DataFrame.applymap has been deprecated. Use DataFrame.map instead.
| 지역 | 연도 | 출생아 수 | |
|---|---|---|---|
| 0 | 서울 | 2010 | 93266.0 |
| 1 | 서울 | 2011 | 91526.0 |
| 2 | 서울 | 2012 | 93914.0 |
| 3 | 서울 | 2013 | 84066.0 |
| 4 | 서울 | 2014 | 83711.0 |
| ... | ... | ... | ... |
| 199 | 제주 | 2017 | 5037.0 |
| 200 | 제주 | 2018 | 4781.0 |
| 201 | 제주 | 2019 | 4500.0 |
| 202 | 제주 | 2020 | 4000.0 |
| 203 | 제주 | 2021 | 3728.0 |
204 rows × 3 columns
df.rename({'지역/연도[6]' : '지역'}, axis = 1).set_index('지역').applymap(lambda x : float(x) if x != '-' else 0).reset_index()\
.drop(17, axis = 0)\
.set_index('지역').stack().reset_index().rename({'level_1' : '연도', 0 : '출생아 수'}, axis = 1)\
.groupby(by = '연도').aggregate({'출생아 수' : 'sum'}).reset_index()C:\Users\hollyriver\AppData\Local\Temp\ipykernel_144\4269128748.py:1: FutureWarning:
DataFrame.applymap has been deprecated. Use DataFrame.map instead.
| 연도 | 출생아 수 | |
|---|---|---|
| 0 | 2010 | 449949.242 |
| 1 | 2011 | 445527.398 |
| 2 | 2012 | 457813.448 |
| 3 | 2013 | 417845.628 |
| 4 | 2014 | 323378.169 |
| 5 | 2015 | 438420.000 |
| 6 | 2016 | 330782.536 |
| 7 | 2017 | 358771.000 |
| 8 | 2018 | 321845.000 |
| 9 | 2019 | 165941.871 |
| 10 | 2020 | 272300.000 |
| 11 | 2021 | 260832.000 |
df.rename({'지역/연도[6]' : '지역'}, axis = 1).set_index('지역').applymap(lambda x : float(x) if x != '-' else 0).reset_index()\
.drop(17, axis = 0)\
.set_index('지역').stack().reset_index().rename({'level_1' : '연도', 0 : '출생아 수'}, axis = 1)\
.groupby(by = '연도').aggregate({'출생아 수' : 'sum'}).reset_index()\
.plot.line(x = '연도', y = '출생아 수', backend = 'plotly')
## 그래프 표기에 한글이 너무나도 잘 나오기 때문에 자신감 있게 한글로 바꿔도 된다!!!C:\Users\hollyriver\AppData\Local\Temp\ipykernel_144\2678187976.py:1: FutureWarning:
DataFrame.applymap has been deprecated. Use DataFrame.map instead.
출생아 수가 팍팍 튀는 구간이 있네용…
시각화 2 : 시도별 출생아 수 시각화(line)
df.rename({'지역/연도[6]' : '지역'}, axis = 1).set_index('지역').map(lambda x : float(x) if x != '-' else 0).reset_index()\
.drop(17, axis = 0)\
.set_index('지역').stack().reset_index().rename({'level_1' : '연도', 0 : '출생아 수'}, axis = 1)\
.pivot_table(index = ['지역', '연도'], values = '출생아 수', aggfunc = 'sum').reset_index() ## 이미 윗줄만으로 충분해...| 지역 | 연도 | 출생아 수 | |
|---|---|---|---|
| 0 | 강원 | 2010 | 12477.0 |
| 1 | 강원 | 2011 | 12408.0 |
| 2 | 강원 | 2012 | 12426.0 |
| 3 | 강원 | 2013 | 10980.0 |
| 4 | 강원 | 2014 | 10662.0 |
| ... | ... | ... | ... |
| 199 | 충북 | 2017 | 11394.0 |
| 200 | 충북 | 2018 | 10586.0 |
| 201 | 충북 | 2019 | 9333.0 |
| 202 | 충북 | 2020 | 8600.0 |
| 203 | 충북 | 2021 | 8190.0 |
204 rows × 3 columns
df.rename({'지역/연도[6]' : '지역'}, axis = 1).set_index('지역').map(lambda x : float(x) if x != '-' else 0).reset_index()\
.drop(17, axis = 0)\
.set_index('지역').stack().reset_index().rename({'level_1' : '연도', 0 : '출생아 수'}, axis = 1)\
.plot.line(x = '연도', y = '출생아 수', color = '지역', backend = 'plotly')경기와 서울이 특정 년도에서 현저히 줄어드는 것을 알 수 있다.
- 두 가지 line plot(전체, 지역별) 각각의 장단점이 있다;; 그 둘을 합쳐서 볼 순 없을까??
### D. 시각화 3 : 시도별 출생아 수 시각화(area)
plot.area()
df.rename({'지역/연도[6]':'지역'},axis=1)\
.set_index(['지역']).map(lambda x: float(x) if not x=='-' else 0)\
.drop('전국',axis=0)\
.stack().reset_index().set_axis(['지역','연도','출생아수'],axis=1)\
.plot.area(x='연도',y='출생아수',color='지역',backend='plotly') ## plot.area()각각 어떻게 변화했는지와, 그 누적의 변화 정도를 시각화할 수 있다.
- 그래서 자꾸 넘어가는 게, 2014 경기, 2016 서울, 2019 서울ㆍ경기 : 출생아가 없었음. 왜 없었어???
df| 지역/연도[6] | 2010 | 2011 | 2012 | 2013 | 2014 | 2015 | 2016 | 2017 | 2018 | 2019 | 2020 | 2021 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 서울 | 93266 | 91526 | 93914.000 | 84066.000 | 83711.000 | 83005 | 75.536 | 65389 | 58074 | 53.673 | 47400 | 45531 |
| 1 | 부산 | 27415 | 27759 | 28673.000 | 25831.000 | 26190.000 | 26645 | 24906.000 | 21480 | 19152 | 17049.000 | 15100 | 14446 |
| 2 | 대구 | 20557 | 20758 | 21472.000 | 19340.000 | 19361.000 | 19438 | 18298.000 | 15946 | 14400 | 13233.000 | 11200 | 10661 |
| 3 | 인천 | 25752 | 20758 | 21472.000 | 25560.000 | 25786.000 | 25491 | 23609.000 | 20445 | 20087 | 18522.000 | 16000 | 14947 |
| 4 | 광주 | 13979 | 13916 | 14392.000 | 12729.000 | 12729.000 | 12441 | 11580.000 | 10120 | 9105 | 8364.000 | 7300 | 7956 |
| 5 | 대전 | 14314 | 14808 | 15279.000 | 14099.000 | 13962.000 | 13774 | 12436.000 | 10851 | 9337 | 8410.000 | 7500 | 7414 |
| 6 | 울산 | 11432 | 11542 | 12160.000 | 11330.000 | 11556.000 | 11732 | 10910.000 | 9381 | 8149 | 7539.000 | 6600 | 6127 |
| 7 | 세종 | - | - | 1054.000 | 1111.000 | 1344.000 | 2708 | 3297.000 | 3504 | 3703 | 3819.000 | 3500 | 3570 |
| 8 | 경기 | 121753 | 122027 | 124746.000 | 112129.000 | 112.169 | 113495 | 105643.000 | 94088 | 83198 | 83.198 | 77800 | 76139 |
| 9 | 강원 | 12477 | 12408 | 12426.000 | 10980.000 | 10662.000 | 10929 | 10058.000 | 9958 | 8351 | 8283.000 | 7800 | 7357 |
| 10 | 충북 | 14670 | 14804 | 15139.000 | 13658.000 | 13366.000 | 13563 | 12742.000 | 11394 | 10586 | 9333.000 | 8600 | 8190 |
| 11 | 충남 | 20.242 | 20.398 | 20.448 | 18.628 | 18200.000 | 18604 | 17302.000 | 15670 | 14380 | 13228.000 | 11900 | 10984 |
| 12 | 전북 | 16100 | 16175 | 16238.000 | 14555.000 | 14231.000 | 14087 | 12698.000 | 11348 | 10001 | 8971.000 | 8200 | 7745 |
| 13 | 전남 | 16654 | 16612 | 16990.000 | 15401.000 | 14817.000 | 15061 | 13980.000 | 12354 | 11238 | 10832.000 | 9700 | 8430 |
| 14 | 경북 | 23700 | 24250 | 24635.000 | 22206.000 | 22062.000 | 22310 | 20616.000 | 17957 | 16079 | 14472.000 | 12900 | 12045 |
| 15 | 경남 | 32203 | 32536 | 33211.000 | 29504.000 | 29763.000 | 29537 | 27138.000 | 23849 | 21224 | 19250.000 | 16800 | 15562 |
| 16 | 제주 | 5657 | 5628 | 5992.000 | 5328.000 | 5526.000 | 5600 | 5494.000 | 5037 | 4781 | 4500.000 | 4000 | 3728 |
| 17 | 전국 | 470171 | 471265 | 484550.000 | 436455.000 | 435435.000 | 438420 | 406243.000 | 357771 | 326822 | 302676.000 | 272400 | 260562 |
경기 2014의 경우 인구수인데 소수점이 존재…(솔로몬???), 서울 2016… 등등. 사람이 쓰다보니 오탈자가 있다.
E. 위에서의 시각화 수정
df.rename({'지역/연도[6]':'지역'},axis=1)\
.set_index(['지역'])\
.map(lambda x: float(x) if not x=='-' else 0)\
.map(lambda x : x if x%1 == 0 else x*1000) ## 소수점이 존재할 경우 그대로, 아닐 경우 1000을 곱해줌.| 2010 | 2011 | 2012 | 2013 | 2014 | 2015 | 2016 | 2017 | 2018 | 2019 | 2020 | 2021 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 지역 | ||||||||||||
| 서울 | 93266.0 | 91526.0 | 93914.0 | 84066.0 | 83711.0 | 83005.0 | 75536.0 | 65389.0 | 58074.0 | 53673.0 | 47400.0 | 45531.0 |
| 부산 | 27415.0 | 27759.0 | 28673.0 | 25831.0 | 26190.0 | 26645.0 | 24906.0 | 21480.0 | 19152.0 | 17049.0 | 15100.0 | 14446.0 |
| 대구 | 20557.0 | 20758.0 | 21472.0 | 19340.0 | 19361.0 | 19438.0 | 18298.0 | 15946.0 | 14400.0 | 13233.0 | 11200.0 | 10661.0 |
| 인천 | 25752.0 | 20758.0 | 21472.0 | 25560.0 | 25786.0 | 25491.0 | 23609.0 | 20445.0 | 20087.0 | 18522.0 | 16000.0 | 14947.0 |
| 광주 | 13979.0 | 13916.0 | 14392.0 | 12729.0 | 12729.0 | 12441.0 | 11580.0 | 10120.0 | 9105.0 | 8364.0 | 7300.0 | 7956.0 |
| 대전 | 14314.0 | 14808.0 | 15279.0 | 14099.0 | 13962.0 | 13774.0 | 12436.0 | 10851.0 | 9337.0 | 8410.0 | 7500.0 | 7414.0 |
| 울산 | 11432.0 | 11542.0 | 12160.0 | 11330.0 | 11556.0 | 11732.0 | 10910.0 | 9381.0 | 8149.0 | 7539.0 | 6600.0 | 6127.0 |
| 세종 | 0.0 | 0.0 | 1054.0 | 1111.0 | 1344.0 | 2708.0 | 3297.0 | 3504.0 | 3703.0 | 3819.0 | 3500.0 | 3570.0 |
| 경기 | 121753.0 | 122027.0 | 124746.0 | 112129.0 | 112169.0 | 113495.0 | 105643.0 | 94088.0 | 83198.0 | 83198.0 | 77800.0 | 76139.0 |
| 강원 | 12477.0 | 12408.0 | 12426.0 | 10980.0 | 10662.0 | 10929.0 | 10058.0 | 9958.0 | 8351.0 | 8283.0 | 7800.0 | 7357.0 |
| 충북 | 14670.0 | 14804.0 | 15139.0 | 13658.0 | 13366.0 | 13563.0 | 12742.0 | 11394.0 | 10586.0 | 9333.0 | 8600.0 | 8190.0 |
| 충남 | 20242.0 | 20398.0 | 20448.0 | 18628.0 | 18200.0 | 18604.0 | 17302.0 | 15670.0 | 14380.0 | 13228.0 | 11900.0 | 10984.0 |
| 전북 | 16100.0 | 16175.0 | 16238.0 | 14555.0 | 14231.0 | 14087.0 | 12698.0 | 11348.0 | 10001.0 | 8971.0 | 8200.0 | 7745.0 |
| 전남 | 16654.0 | 16612.0 | 16990.0 | 15401.0 | 14817.0 | 15061.0 | 13980.0 | 12354.0 | 11238.0 | 10832.0 | 9700.0 | 8430.0 |
| 경북 | 23700.0 | 24250.0 | 24635.0 | 22206.0 | 22062.0 | 22310.0 | 20616.0 | 17957.0 | 16079.0 | 14472.0 | 12900.0 | 12045.0 |
| 경남 | 32203.0 | 32536.0 | 33211.0 | 29504.0 | 29763.0 | 29537.0 | 27138.0 | 23849.0 | 21224.0 | 19250.0 | 16800.0 | 15562.0 |
| 제주 | 5657.0 | 5628.0 | 5992.0 | 5328.0 | 5526.0 | 5600.0 | 5494.0 | 5037.0 | 4781.0 | 4500.0 | 4000.0 | 3728.0 |
| 전국 | 470171.0 | 471265.0 | 484550.0 | 436455.0 | 435435.0 | 438420.0 | 406243.0 | 357771.0 | 326822.0 | 302676.0 | 272400.0 | 260562.0 |
df.rename({'지역/연도[6]':'지역'},axis=1)\
.set_index(['지역'])\
.map(lambda x: float(x) if not x=='-' else 0)\
.map(lambda x : x if x%1 == 0 else x*1000)\
.stack().reset_index().rename({'level_1' : '연도', 0 : '출생아 수'}, axis = 1)\
.groupby('연도').aggregate({'출생아 수' : 'sum'}).reset_index()\
.plot.line(x = '연도', y = '출생아 수', backend = 'plotly')사실 튀는 구간 따위는 없었다!!!(그치만 줄어들고 있는건 사실이었네…)
df.rename({'지역/연도[6]':'지역'},axis=1)\
.set_index(['지역'])\
.map(lambda x: float(x) if not x=='-' else 0)\
.map(lambda x : x if x%1 == 0 else x*1000)\
.stack().reset_index().set_axis(['지역','연도','출생아수'],axis=1)\
.plot.line(x = '연도', y = '출생아수', color = '지역', backend = 'plotly')df.rename({'지역/연도[6]':'지역'},axis=1)\
.set_index(['지역'])\
.map(lambda x: float(x) if not x=='-' else 0)\
.map(lambda x : x if x%1 == 0 else x*1000)\
.stack().reset_index().set_axis(['지역','연도','출생아수'],axis=1)\
.plot.area(x = '연도', y = '출생아수', color = '지역', backend = 'plotly')정상적으로 이해할 수 있다.
4. 여러가지 플랏
plotly는bar,line,scatter,hist,box,area형태의 플랏을 지원한다.(pie나 그런 것들은 지원안함!)
### A. .plot.bar()
- 예제 1 : 성별 별 합격률 시각화
df = pd.read_csv("https://raw.githubusercontent.com/guebin/DV2022/master/posts/Simpson.csv",index_col=0,header=[0,1]).reset_index().melt(id_vars='index').set_axis(['department','gender','result','count'],axis=1)
df ## 파일에 index_column이 존재하고, 첫 행이 열이름인듯.| department | gender | result | count | |
|---|---|---|---|---|
| 0 | A | male | fail | 314 |
| 1 | B | male | fail | 208 |
| 2 | C | male | fail | 204 |
| 3 | D | male | fail | 279 |
| 4 | E | male | fail | 137 |
| 5 | F | male | fail | 149 |
| 6 | A | male | pass | 511 |
| 7 | B | male | pass | 352 |
| 8 | C | male | pass | 121 |
| 9 | D | male | pass | 138 |
| 10 | E | male | pass | 54 |
| 11 | F | male | pass | 224 |
| 12 | A | female | fail | 19 |
| 13 | B | female | fail | 7 |
| 14 | C | female | fail | 391 |
| 15 | D | female | fail | 244 |
| 16 | E | female | fail | 299 |
| 17 | F | female | fail | 103 |
| 18 | A | female | pass | 89 |
| 19 | B | female | pass | 18 |
| 20 | C | female | pass | 202 |
| 21 | D | female | pass | 131 |
| 22 | E | female | pass | 94 |
| 23 | F | female | pass | 238 |
어디서 많이 봤던 데이터, 집단 간 비교니까 바차트가 좋겠죠잉
df.pivot_table(index = 'gender', columns = 'result', values = 'count', aggfunc = 'sum')\
.assign(rate = lambda _df : _df['pass']/(_df.fail + _df['pass'])).reset_index()\
.assign(rate = lambda _df : _df.rate.apply(lambda x : round(x, 3)))\
.plot.bar(x = 'gender', y = 'rate', color = 'gender', text = 'rate', width = 600)# 예제 2 : (성별, 학과) 별 지원자 수 시각화
df.pivot_table(index = ['gender', 'department'], columns = 'result', values = 'count', aggfunc = 'sum')\
.assign(rate = lambda _df : _df['pass']/(_df.fail + _df['pass'])).reset_index()\
.assign(rate = lambda _df : _df.rate.apply(lambda x : round(x, 2)))\
.plot.bar(x = 'gender', y = 'rate', color = 'gender', facet_col = 'department', text = 'rate', width = 800)B. .plot.line()
# 예제 1 : 핸드폰 판매량
df = pd.read_csv('https://raw.githubusercontent.com/guebin/2021DV/master/_notebooks/phone.csv')
df| Date | Samsung | Apple | Huawei | Xiaomi | Oppo | Mobicel | Motorola | LG | Others | Realme | Nokia | Lenovo | OnePlus | Sony | Asus | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2019-10 | 461 | 324 | 136 | 109 | 76 | 81 | 43 | 37 | 135 | 28 | 39 | 14 | 22 | 17 | 20 | 17 |
| 1 | 2019-11 | 461 | 358 | 167 | 141 | 86 | 61 | 29 | 36 | 141 | 27 | 29 | 20 | 23 | 10 | 19 | 27 |
| 2 | 2019-12 | 426 | 383 | 143 | 105 | 53 | 45 | 51 | 48 | 129 | 30 | 20 | 26 | 28 | 18 | 18 | 19 |
| 3 | 2020-01 | 677 | 494 | 212 | 187 | 110 | 79 | 65 | 49 | 158 | 23 | 13 | 19 | 19 | 22 | 27 | 22 |
| 4 | 2020-02 | 593 | 520 | 217 | 195 | 112 | 67 | 62 | 71 | 157 | 25 | 18 | 16 | 24 | 18 | 23 | 20 |
| 5 | 2020-03 | 637 | 537 | 246 | 187 | 92 | 66 | 59 | 67 | 145 | 21 | 16 | 24 | 18 | 31 | 22 | 14 |
| 6 | 2020-04 | 647 | 583 | 222 | 154 | 98 | 59 | 48 | 64 | 113 | 20 | 23 | 25 | 19 | 19 | 23 | 21 |
| 7 | 2020-05 | 629 | 518 | 192 | 176 | 91 | 87 | 50 | 66 | 150 | 43 | 27 | 15 | 18 | 19 | 19 | 13 |
| 8 | 2020-06 | 663 | 552 | 209 | 185 | 93 | 69 | 54 | 60 | 140 | 39 | 16 | 16 | 17 | 29 | 25 | 16 |
| 9 | 2020-07 | 599 | 471 | 214 | 193 | 89 | 78 | 65 | 59 | 130 | 40 | 27 | 25 | 21 | 18 | 18 | 12 |
| 10 | 2020-08 | 615 | 567 | 204 | 182 | 105 | 82 | 62 | 42 | 129 | 47 | 16 | 23 | 21 | 27 | 23 | 20 |
| 11 | 2020-09 | 621 | 481 | 230 | 220 | 102 | 88 | 56 | 49 | 143 | 54 | 14 | 15 | 17 | 15 | 19 | 15 |
| 12 | 2020-10 | 637 | 555 | 232 | 203 | 90 | 52 | 63 | 49 | 140 | 33 | 17 | 20 | 22 | 9 | 22 | 21 |
시계열 자료니까 라인차트로 그리면 좋겠다잉
df.melt(id_vars = ['Date']).set_axis(['날짜', '회사', '판매량'], axis = 1)\
.plot.line(x = '날짜', y = '판매량', color = '회사') ## 어떻게 날짜로 잘 읽었네.### C. .plot.scatter()
position_dict = {
'GOALKEEPER':{'GK'},
'DEFENDER':{'CB','RCB','LCB','RB','LB','RWB','LWB'},
'MIDFIELDER':{'CM','RCM','LCM','CDM','RDM','LDM','CAM','RAM','LAM','RM','LM'},
'FORWARD':{'ST','CF','RF','LF','RW','LW','RS','LS'},
'SUB':{'SUB'},
'RES':{'RES'}
}
df = pd.read_csv('https://raw.githubusercontent.com/guebin/DV2021/master/_notebooks/2021-10-25-FIFA22_official_data.csv')\
.loc[:,lambda df: df.isna().mean()<0.5].dropna()\
.assign(Position = lambda df: df.Position.str.split(">").str[-1].apply(lambda x: [k for k,v in position_dict.items() if x in v].pop()))\
.assign(Wage = lambda df: df.Wage.str[1:].str.replace('K','000').astype(int))
df| ID | Name | Age | Photo | Nationality | Flag | Overall | Potential | Club | Club Logo | ... | SlidingTackle | GKDiving | GKHandling | GKKicking | GKPositioning | GKReflexes | Best Position | Best Overall Rating | Release Clause | DefensiveAwareness | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 212198 | Bruno Fernandes | 26 | https://cdn.sofifa.com/players/212/198/22_60.png | Portugal | https://cdn.sofifa.com/flags/pt.png | 88 | 89 | Manchester United | https://cdn.sofifa.com/teams/11/30.png | ... | 65.0 | 12.0 | 14.0 | 15.0 | 8.0 | 14.0 | CAM | 88.0 | €206.9M | 72.0 |
| 1 | 209658 | L. Goretzka | 26 | https://cdn.sofifa.com/players/209/658/22_60.png | Germany | https://cdn.sofifa.com/flags/de.png | 87 | 88 | FC Bayern München | https://cdn.sofifa.com/teams/21/30.png | ... | 77.0 | 13.0 | 8.0 | 15.0 | 11.0 | 9.0 | CM | 87.0 | €160.4M | 74.0 |
| 2 | 176580 | L. Suárez | 34 | https://cdn.sofifa.com/players/176/580/22_60.png | Uruguay | https://cdn.sofifa.com/flags/uy.png | 88 | 88 | Atlético de Madrid | https://cdn.sofifa.com/teams/240/30.png | ... | 38.0 | 27.0 | 25.0 | 31.0 | 33.0 | 37.0 | ST | 88.0 | €91.2M | 42.0 |
| 3 | 192985 | K. De Bruyne | 30 | https://cdn.sofifa.com/players/192/985/22_60.png | Belgium | https://cdn.sofifa.com/flags/be.png | 91 | 91 | Manchester City | https://cdn.sofifa.com/teams/10/30.png | ... | 53.0 | 15.0 | 13.0 | 5.0 | 10.0 | 13.0 | CM | 91.0 | €232.2M | 68.0 |
| 4 | 224334 | M. Acuña | 29 | https://cdn.sofifa.com/players/224/334/22_60.png | Argentina | https://cdn.sofifa.com/flags/ar.png | 84 | 84 | Sevilla FC | https://cdn.sofifa.com/teams/481/30.png | ... | 82.0 | 8.0 | 14.0 | 13.0 | 13.0 | 14.0 | LB | 84.0 | €77.7M | 80.0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 16703 | 259718 | F. Gebhardt | 19 | https://cdn.sofifa.com/players/259/718/22_60.png | Germany | https://cdn.sofifa.com/flags/de.png | 52 | 66 | FC Basel 1893 | https://cdn.sofifa.com/teams/896/30.png | ... | 10.0 | 53.0 | 45.0 | 47.0 | 52.0 | 57.0 | GK | 52.0 | €361K | 6.0 |
| 16704 | 251433 | B. Voll | 20 | https://cdn.sofifa.com/players/251/433/22_60.png | Germany | https://cdn.sofifa.com/flags/de.png | 58 | 69 | F.C. Hansa Rostock | https://cdn.sofifa.com/teams/27/30.png | ... | 10.0 | 59.0 | 60.0 | 56.0 | 55.0 | 61.0 | GK | 58.0 | €656K | 5.0 |
| 16706 | 262846 | �. Dobre | 20 | https://cdn.sofifa.com/players/262/846/22_60.png | Romania | https://cdn.sofifa.com/flags/ro.png | 53 | 63 | FC Academica Clinceni | https://cdn.sofifa.com/teams/113391/30.png | ... | 12.0 | 57.0 | 52.0 | 53.0 | 48.0 | 58.0 | GK | 53.0 | €279K | 5.0 |
| 16707 | 241317 | 21 Xue Qinghao | 19 | https://cdn.sofifa.com/players/241/317/21_60.png | China PR | https://cdn.sofifa.com/flags/cn.png | 47 | 60 | Shanghai Shenhua FC | https://cdn.sofifa.com/teams/110955/30.png | ... | 9.0 | 49.0 | 48.0 | 45.0 | 38.0 | 52.0 | GK | 47.0 | €223K | 21.0 |
| 16708 | 259646 | A. Shaikh | 18 | https://cdn.sofifa.com/players/259/646/22_60.png | India | https://cdn.sofifa.com/flags/in.png | 47 | 67 | ATK Mohun Bagan FC | https://cdn.sofifa.com/teams/113146/30.png | ... | 13.0 | 49.0 | 41.0 | 39.0 | 45.0 | 49.0 | GK | 47.0 | €259K | 7.0 |
14398 rows × 63 columns
가슴아픈 데이터, 연속형 변수 간 관계를 보고 싶으니 산점도가 적당
df.query('Position == "DEFENDER" or Position == "FORWARD"')\
.plot.scatter(x = 'ShotPower', y = 'StandingTackle',
color = 'Position', size = 'Wage', hover_data = ['Name', 'Age'],
opacity = 0.5, width = 800) ## hover_data로 마우스를 가져다 대었을 때 표기되는 정보를 알 수 있으며, alpha대신 opacity 옵션으로 투명도 조절D. .plot.box()
# 예제 1 : 렛츠고! 전북고등학교!
y1=[75,75,76,76,77,77,78,79,79,98] # A선생님에게 통계학을 배운 학생의 점수들
y2=[76,76,77,77,78,78,79,80,80,81] # B선생님에게 통계학을 배운 학생의 점수들df = pd.DataFrame({
'Class' : ['A']*len(y1) + ['B']*len(y2),
'Score' : y1+y2
})
df| Class | Score | |
|---|---|---|
| 0 | A | 75 |
| 1 | A | 75 |
| 2 | A | 76 |
| 3 | A | 76 |
| 4 | A | 77 |
| 5 | A | 77 |
| 6 | A | 78 |
| 7 | A | 79 |
| 8 | A | 79 |
| 9 | A | 98 |
| 10 | B | 76 |
| 11 | B | 76 |
| 12 | B | 77 |
| 13 | B | 77 |
| 14 | B | 78 |
| 15 | B | 78 |
| 16 | B | 79 |
| 17 | B | 80 |
| 18 | B | 80 |
| 19 | B | 81 |
df.plot.box(x = 'Class', y = 'Score', color = 'Class',
points = 'all', width = 500, backend = 'plotly') ## points를 'outlier'로 설정하면 # 예제 2 : (년도, 시도) 별 전기 에너지 사용량
url = 'https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/{}.csv'
prov = ['Seoul', 'Busan', 'Daegu', 'Incheon',
'Gwangju', 'Daejeon', 'Ulsan', 'Sejongsi',
'Gyeonggi-do', 'Gangwon-do', 'Chungcheongbuk-do',
'Chungcheongnam-do', 'Jeollabuk-do', 'Jeollanam-do',
'Gyeongsangbuk-do', 'Gyeongsangnam-do', 'Jeju-do']
df = pd.concat([pd.read_csv(url.format(p+y)).assign(년도=y, 시도=p) for p in prov for y in ['2018', '2019', '2020', '2021']]).reset_index(drop=True)\
.assign(년도 = lambda df: df.년도.astype(int))\
.set_index(['년도','시도','지역']).applymap(lambda x: int(str(x).replace(',','')))\
.reset_index()
df.head()C:\Users\hollyriver\AppData\Local\Temp\ipykernel_12860\2652719362.py:9: FutureWarning:
DataFrame.applymap has been deprecated. Use DataFrame.map instead.
| 년도 | 시도 | 지역 | 건물동수 | 연면적 | 에너지사용량(TOE)/전기 | 에너지사용량(TOE)/도시가스 | 에너지사용량(TOE)/지역난방 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2018 | Seoul | 종로구 | 17929 | 9141777 | 64818 | 82015 | 111 |
| 1 | 2018 | Seoul | 중구 | 10598 | 10056233 | 81672 | 75260 | 563 |
| 2 | 2018 | Seoul | 용산구 | 17201 | 10639652 | 52659 | 85220 | 12043 |
| 3 | 2018 | Seoul | 성동구 | 14180 | 11631770 | 60559 | 107416 | 0 |
| 4 | 2018 | Seoul | 광진구 | 21520 | 12054796 | 70609 | 130308 | 0 |
df.plot.box(x = '시도', y = '에너지사용량(TOE)/전기', color = '시도', facet_row = '년도', width = 800, height = 1000, hover_data = ['지역', '연면적'], points = 'outliers') ## outliers가 디폴트임### E. .plot.hist()
# 예제 1 : 타이타닉 (연령, 성별) 별 생존자
df = pd.read_csv("https://raw.githubusercontent.com/guebin/DV2023/main/posts/titanic.csv")
df| PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | logFare | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 0 | 3 | Braund, Mr. Owen Harris | male | 22.0 | 1 | 0 | A/5 21171 | 7.2500 | NaN | S | 1.981001 |
| 1 | 2 | 1 | 1 | Cumings, Mrs. John Bradley (Florence Briggs Th... | female | 38.0 | 1 | 0 | PC 17599 | 71.2833 | C85 | C | 4.266662 |
| 2 | 3 | 1 | 3 | Heikkinen, Miss. Laina | female | 26.0 | 0 | 0 | STON/O2. 3101282 | 7.9250 | NaN | S | 2.070022 |
| 3 | 4 | 1 | 1 | Futrelle, Mrs. Jacques Heath (Lily May Peel) | female | 35.0 | 1 | 0 | 113803 | 53.1000 | C123 | S | 3.972177 |
| 4 | 5 | 0 | 3 | Allen, Mr. William Henry | male | 35.0 | 0 | 0 | 373450 | 8.0500 | NaN | S | 2.085672 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 886 | 887 | 0 | 2 | Montvila, Rev. Juozas | male | 27.0 | 0 | 0 | 211536 | 13.0000 | NaN | S | 2.564949 |
| 887 | 888 | 1 | 1 | Graham, Miss. Margaret Edith | female | 19.0 | 0 | 0 | 112053 | 30.0000 | B42 | S | 3.401197 |
| 888 | 889 | 0 | 3 | Johnston, Miss. Catherine Helen "Carrie" | female | NaN | 1 | 2 | W./C. 6607 | 23.4500 | NaN | S | 3.154870 |
| 889 | 890 | 1 | 1 | Behr, Mr. Karl Howell | male | 26.0 | 0 | 0 | 111369 | 30.0000 | C148 | C | 3.401197 |
| 890 | 891 | 0 | 3 | Dooley, Mr. Patrick | male | 32.0 | 0 | 0 | 370376 | 7.7500 | NaN | Q | 2.047693 |
891 rows × 13 columns
df.Age.hist()시리즈를 통해 아무 입력 없이 히스토그램을 그릴 수도 있다.
df.plot.hist(x = 'Age', color = 'Sex',
facet_row = 'Sex', facet_col = 'Survived')성별 간 효과가 저연령에서는 옅어지는 것이 보인다.(생존률로 시각화해도 좋을듯)
df.plot.hist(x = 'Age', color = 'Survived', facet_col = 'Sex')df.loc[:, ['Age', 'Sex', 'Survived']].assign(Age_cut = lambda _df : pd.qcut(_df.Age, q = 10))\
.pivot_table(index = 'Age_cut', columns = 'Sex', values = 'Survived', aggfunc = 'mean')\
.stack().reset_index().rename({0 : 'Rate'}, axis = 1)\
.plot.bar(x = 'Sex', y = 'Rate', color = 'Sex', facet_col = 'Age_cut', width = 800)C:\Users\hollyriver\anaconda3\envs\py\lib\site-packages\plotly\express\_core.py:2044: FutureWarning:
The default of observed=False is deprecated and will be changed to True in a future version of pandas. Pass observed=False to retain current behavior or observed=True to adopt the future default and silence this warning.
세부조정은 알아서 하시길…
F. .plot.area()
# 예제 1 : 핸드폰 판매량
df = pd.read_csv('https://raw.githubusercontent.com/guebin/2021DV/master/_notebooks/phone.csv')
df| Date | Samsung | Apple | Huawei | Xiaomi | Oppo | Mobicel | Motorola | LG | Others | Realme | Nokia | Lenovo | OnePlus | Sony | Asus | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2019-10 | 461 | 324 | 136 | 109 | 76 | 81 | 43 | 37 | 135 | 28 | 39 | 14 | 22 | 17 | 20 | 17 |
| 1 | 2019-11 | 461 | 358 | 167 | 141 | 86 | 61 | 29 | 36 | 141 | 27 | 29 | 20 | 23 | 10 | 19 | 27 |
| 2 | 2019-12 | 426 | 383 | 143 | 105 | 53 | 45 | 51 | 48 | 129 | 30 | 20 | 26 | 28 | 18 | 18 | 19 |
| 3 | 2020-01 | 677 | 494 | 212 | 187 | 110 | 79 | 65 | 49 | 158 | 23 | 13 | 19 | 19 | 22 | 27 | 22 |
| 4 | 2020-02 | 593 | 520 | 217 | 195 | 112 | 67 | 62 | 71 | 157 | 25 | 18 | 16 | 24 | 18 | 23 | 20 |
| 5 | 2020-03 | 637 | 537 | 246 | 187 | 92 | 66 | 59 | 67 | 145 | 21 | 16 | 24 | 18 | 31 | 22 | 14 |
| 6 | 2020-04 | 647 | 583 | 222 | 154 | 98 | 59 | 48 | 64 | 113 | 20 | 23 | 25 | 19 | 19 | 23 | 21 |
| 7 | 2020-05 | 629 | 518 | 192 | 176 | 91 | 87 | 50 | 66 | 150 | 43 | 27 | 15 | 18 | 19 | 19 | 13 |
| 8 | 2020-06 | 663 | 552 | 209 | 185 | 93 | 69 | 54 | 60 | 140 | 39 | 16 | 16 | 17 | 29 | 25 | 16 |
| 9 | 2020-07 | 599 | 471 | 214 | 193 | 89 | 78 | 65 | 59 | 130 | 40 | 27 | 25 | 21 | 18 | 18 | 12 |
| 10 | 2020-08 | 615 | 567 | 204 | 182 | 105 | 82 | 62 | 42 | 129 | 47 | 16 | 23 | 21 | 27 | 23 | 20 |
| 11 | 2020-09 | 621 | 481 | 230 | 220 | 102 | 88 | 56 | 49 | 143 | 54 | 14 | 15 | 17 | 15 | 19 | 15 |
| 12 | 2020-10 | 637 | 555 | 232 | 203 | 90 | 52 | 63 | 49 | 140 | 33 | 17 | 20 | 22 | 9 | 22 | 21 |
df.melt(id_vars = 'Date').set_axis(['날짜', '회사', '판매량'], axis = 1)\
.plot.area(x = '날짜', y = '판매량', color = '회사')전체적인 판매량과 개별 판매량의 정도를 알 수 있음. (두 플랏을 한 번에!)
# 예제 2 : 에너지 사용량
url = 'https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/{}.csv'
prov = ['Seoul', 'Busan', 'Daegu', 'Incheon',
'Gwangju', 'Daejeon', 'Ulsan', 'Sejongsi',
'Gyeonggi-do', 'Gangwon-do', 'Chungcheongbuk-do',
'Chungcheongnam-do', 'Jeollabuk-do', 'Jeollanam-do',
'Gyeongsangbuk-do', 'Gyeongsangnam-do', 'Jeju-do']
df = pd.concat([pd.read_csv(url.format(p+y)).assign(년도=y, 시도=p) for p in prov for y in ['2018', '2019', '2020', '2021']]).reset_index(drop=True)\
.assign(년도 = lambda df: df.년도.astype(int))\
.set_index(['년도','시도','지역']).applymap(lambda x: int(str(x).replace(',','')))\
.reset_index()
df.head()C:\Users\hollyriver\AppData\Local\Temp\ipykernel_12860\2652719362.py:9: FutureWarning:
DataFrame.applymap has been deprecated. Use DataFrame.map instead.
| 년도 | 시도 | 지역 | 건물동수 | 연면적 | 에너지사용량(TOE)/전기 | 에너지사용량(TOE)/도시가스 | 에너지사용량(TOE)/지역난방 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2018 | Seoul | 종로구 | 17929 | 9141777 | 64818 | 82015 | 111 |
| 1 | 2018 | Seoul | 중구 | 10598 | 10056233 | 81672 | 75260 | 563 |
| 2 | 2018 | Seoul | 용산구 | 17201 | 10639652 | 52659 | 85220 | 12043 |
| 3 | 2018 | Seoul | 성동구 | 14180 | 11631770 | 60559 | 107416 | 0 |
| 4 | 2018 | Seoul | 광진구 | 21520 | 12054796 | 70609 | 130308 | 0 |
df.set_index(['년도','시도','지역','건물동수','연면적']).stack().reset_index().rename({'level_5' : '에너지종류', 0 : '에너지사용량'}, axis = 1)\
.assign(에너지종류 = lambda _df : _df.에너지종류.str.split('/').apply(lambda x : x[-1]))\
.pivot_table(index = ['년도', '시도', '에너지종류'], values = '에너지사용량', aggfunc = 'sum')\
.reset_index().plot.area(x = '년도', y = '에너지사용량', facet_col = '에너지종류', color = '시도', width = 600)## figure로 저장
fig = df.set_index(['년도','시도','지역','건물동수','연면적']).stack().reset_index().rename({'level_5' : '에너지종류', 0 : '에너지사용량'}, axis = 1)\
.assign(에너지종류 = lambda _df : _df.에너지종류.str.split('/').str[-1])\
.pivot_table(index = ['에너지종류', '시도', '년도'], values = '에너지사용량', aggfunc = 'sum').reset_index()\
.plot.area(x = '년도', y = '에너지사용량', facet_col = '에너지종류', color = '시도', width = 600)
## 아마도 xaxis의 범위를 한정하는 과정에서 xlabel의 범위가 조정되다보니 이리 되는듯.
fig.update_layout(
xaxis_domain = [0.0, 0.25],
xaxis2_domain = [0.35, 0.60],
xaxis3_domain = [0.70, 0.95]
)겹칠땐 이렇게 하면 됩니다… 근데 좁아진 걸 보니 다르게 개선할 수도 있을 것 같기는 함…